package auth

import (
	"errors"
	"log/slog"
	"net"
	"net/http"
	"net/url"

	"github.com/go-redis/redis"
)

var ErrAuthenticateFailed = errors.New("authenticate failed")

type Authenticator interface {
	// Authenticate sid认证
	Authenticate(sid string) error
	// Authenticate1 http request 认证
	Authenticate1(*http.Request) error
}

// AnonymousAuthenticator 匿名认证器，允许所有连接
type AnonymousAuthenticator struct {
}

func (a *AnonymousAuthenticator) Authenticate(string) error {
	return nil
}

func (a *AnonymousAuthenticator) Authenticate1(*http.Request) error {
	return nil
}

// RedisAuthenticator 从 redis 查询sid，确定 sid 是否有效
type RedisAuthenticator struct {
	client           *redis.Client
	sessionKeyPrefix string
	logger           *slog.Logger
}

func NewRedisAuthenticator(addr, password string, db int, logger *slog.Logger) *RedisAuthenticator {
	return &RedisAuthenticator{
		client: redis.NewClient(&redis.Options{
			Addr:     addr,
			Password: password,
			DB:       db,
		}),
		sessionKeyPrefix: "PHPREDIS_SESSION:",
		logger:           logger,
	}
}

func (a *RedisAuthenticator) Authenticate(sid string) error {
	a.logger.Debug("authenticating", "sid", sid)
	if len(sid) == 0 {
		return ErrAuthenticateFailed
	}

	cmd := a.client.Exists(a.sessionKeyPrefix + sid)
	if cmd.Err() != nil {
		return cmd.Err()
	}
	if cmd.Val() == 0 {
		return ErrAuthenticateFailed
	}
	return nil
}

func (s *RedisAuthenticator) Authenticate1(r *http.Request) error {
	getSID := func() string {
		sid := r.URL.Query().Get("sid")
		if sid == "" {
			sid = r.Header.Get("X-SID")
		}
		if len(sid) > 0 {
			return sid
		}

		origin, err := url.Parse(r.Header.Get("origin"))
		if err != nil {
			s.logger.Error("parse origin failed", "err", err)
			return ""
		}

		originHost, _, err := net.SplitHostPort(origin.Host)
		if err != nil {
			originHost = origin.Host
		}
		host, _, err := net.SplitHostPort(r.Host)
		if err != nil {
			host = r.Host
		}

		if originHost != "" && originHost != host {
			s.logger.Error("origin and host are different", "origin", originHost, "host", host)
			return ""
		}
		cookie, err := r.Cookie("TMSESSNAME") // Cookie
		if err != nil {
			s.logger.Error("get cookie failed", "err", err)
			return ""
		}
		return cookie.Value
	}
	return s.Authenticate(getSID())
}
